#pragma once
#include "encrypt.h"
#include "indirect_object.h"
#include "scanner/parser.h"
#include <memory>

namespace Pdf
{
	struct BrokenPdf : public std::runtime_error
	{
		BrokenPdf() : std::runtime_error("Pdf::BrokenPdf") {    }
	};

	class Encrypt2;

	class InputDocument : public Parser
    {
    public:
		typedef std::vector<char>       Binary;

		InputDocument(std::istream *input, const std::string &password, bool repair);
        ~InputDocument();

        Dictionary::Ptr                 getTrailer() throw();
        std::string                     getHeaderVersion() const throw();
        std::string                     getHeaderBinary() const throw();
		int								getPageCount();
		IndirectObject					getPage(int number);
		IndirectObject					getCatalog() const throw();
		IndirectObject					getPageRoot() const throw();
		IndirectObject					getIndirectObject(Reference::Ptr ref);

        Object::Ptr                     resolve(Object::Ptr obj);

        template <typename TargetType>
        typename TargetType::Ptr        resolve(Object::Ptr obj);

    protected:
		typedef std::istream::pos_type              PosType;
		typedef std::istream::off_type              OffType;
		typedef std::map<Reference::Value, PosType> RefTable;
		typedef std::vector<IndirectObject>			PageList;

        class Resolver;
		class RefTableLoader;

		void                            getLine(std::string &line);
		PosType                         findKeyword(const std::string &keyword);
        Integer::Ptr                    getStartXRef();
        void                            getRefTable(Integer::Ptr startxref);
		void							fixRefTable();
		void							getPages();
		void							getPages(Dictionary::Ptr parent);

		std::istream*					input;
        RefTable                        reftable;
		std::string                     hdrVersion;
		std::string                     hdrBinary;
        Dictionary::Ptr                 trailer;
		IndirectObject					catalog;
		IndirectObject					pages;
		PageList                        pageList;
		std::auto_ptr<Encrypt>          encrypt;
    };

    // =====================================================================

    inline Dictionary::Ptr InputDocument::getTrailer()
    {    return trailer;    }

    inline std::string InputDocument::getHeaderVersion() const
    {   return hdrVersion;  }

    inline std::string InputDocument::getHeaderBinary() const
    {   return hdrBinary;   }

	inline int InputDocument::getPageCount()
	{
		if ( pageList.empty() )
			getPages();
		return static_cast<int>( pageList.size() ); 
	}

	inline IndirectObject InputDocument::getPage(int number)
	{
		if ( pageList.empty() )
			getPages();
		return pageList[number];	
	}

	inline IndirectObject InputDocument::getCatalog() const
	{	return catalog;	}

	inline IndirectObject InputDocument::getPageRoot() const
	{	return pages;	}

	template <typename TargetType>
	typename TargetType::Ptr InputDocument::resolve(Object::Ptr obj)
	{
		obj = resolve( obj );
		return TargetType::Ptr( obj ? cast<TargetType*>( obj ) : 0 );
	}
}
